﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Services.Client;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System.Diagnostics.Contracts;

namespace AzureToolkit
{
    public abstract class AzureTableBase<T> : IAzureTable<T>
    {
        private readonly CloudStorageAccount account;

        public AzureTableBase(CloudStorageAccount account, string tableName)
        {
            Contract.Requires(account != null);

            if (String.IsNullOrEmpty(tableName))
            {
                tableName = typeof(T).Name.ToLowerInvariant();
                if (tableName.EndsWith("entity") && tableName.Length > 6)
                {
                    tableName = tableName.Substring(0, tableName.Length - 6);
                }
                else if (tableName.EndsWith("row") && tableName.Length > 3)
                {
                    tableName = tableName.Substring(0, tableName.Length - 3);
                }
            }

            this.TableName = tableName;
            this.account = account;
        }

        public string TableName { get; set; }

        public void Initialize()
        {
            var cloudTableClient = new CloudTableClient(this.account.TableEndpoint.ToString(), this.account.Credentials);
            cloudTableClient.CreateTableIfNotExist(this.TableName);
        }

        public virtual IQueryable<T> Query
        {
            get
            {
                TableServiceContext context = CreateContext();
                return context.CreateQuery<T>(TableName).AsTableServiceQuery();
            }
        }

        public virtual T Get(Expression<Func<T, bool>> predicate)
        {
            return this.Query.Where(predicate).FirstOrDefault();
        }

        public void Add(T obj)
        {
            this.Add(new[] { obj });
        }

        public virtual void Add(IEnumerable<T> objs)
        {
            TableServiceContext context = this.CreateContext();

            foreach (var obj in objs)
            {
                context.AddObject(TableName, obj);
            }

            var saveChangesOptions = SaveChangesOptions.None;
            if (objs.Count() == 1)
            {
                saveChangesOptions = SaveChangesOptions.Batch;
            }

            context.SaveChanges(saveChangesOptions);
        }

        public void AddOrUpdate(T obj)
        {
            this.AddOrUpdate(new[] { obj });
        }

        public abstract void AddOrUpdate(IEnumerable<T> objs);

        public ICollection<T> GetAll()
        {
            return GetAll(null);
        }

        public virtual ICollection<T> GetAll(Expression<Func<T, bool>> predicate)
        {
            var context = CreateContext();
            int take = 1000;
            ListRowsContinuationToken continuationToken = null;
            var rows = new Collection<T>();
            do
            {
                var query = context.CreateQuery<T>(TableName);
                query = query.Take(take) as DataServiceQuery<T>;
                if (predicate != null)
                {
                    query = query.Where(predicate) as DataServiceQuery<T>;
                }
                if (continuationToken != null)
                {
                    query = query.AddQueryOption("NextPartitionKey", continuationToken.PartitionKey);
                    if (continuationToken.RowKey != null)
                    {
                        query = query.AddQueryOption("NextRowKey", continuationToken.RowKey);
                    }
                }

                var response = query.Execute() as QueryOperationResponse;
                foreach (T row in response)
                {
                    rows.Add(row);
                }
                if (response.Headers.ContainsKey("x-ms-continuation-NextPartitionKey"))
                {
                    continuationToken = new ListRowsContinuationToken();
                    continuationToken.PartitionKey = response.Headers["x-ms-continuation-NextPartitionKey"];
                    if (response.Headers.ContainsKey("x-ms-continuation-NextRowKey"))
                    {
                        continuationToken.RowKey = response.Headers["x-ms-continuation-NextRowKey"];
                    }
                }
                else
                {
                    continuationToken = null;
                }
            } while (continuationToken != null);
            return rows;
        }

        public void Delete(T obj)
        {
            this.Delete(new[] { obj });
        }


        public virtual void Delete(IEnumerable<T> objs)
        {
            TableServiceContext context = this.CreateContext();
            foreach (var obj in objs)
            {
                context.AttachTo(TableName, obj, "*");
                context.DeleteObject(obj);
            }

            try
            {
                context.SaveChanges();
            }
            catch (DataServiceRequestException ex)
            {
                var dataServiceClientException = ex.InnerException as DataServiceClientException;
                if (dataServiceClientException != null)
                {
                    if (dataServiceClientException.StatusCode == 404)
                    {
                        return;
                    }
                }

                throw;
            }
        }

        protected TableServiceContext CreateContext()
        {
            var context = new TableServiceContext(this.account.TableEndpoint.ToString(), this.account.Credentials);
            context.IgnoreResourceNotFoundException = true;
            context.IgnoreMissingProperties = true;
            return context;
        }

        //private class PartitionKeyComparer : IEqualityComparer<TableServiceEntity>
        //{
        //    public bool Equals(TableServiceEntity x, TableServiceEntity y)
        //    {
        //        return string.Compare(x.PartitionKey, y.PartitionKey, StringComparison.OrdinalIgnoreCase) == 0;
        //    }

        //    public int GetHashCode(TableServiceEntity obj)
        //    {
        //        return obj.PartitionKey.GetHashCode();
        //    }
        //}
    }
}
